﻿///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Область ПрограммныйИнтерфейс

// Возвращает даты, которые отличаются указанной даты ДатаОт на количество дней,
// входящих в указанный график ГрафикРаботы.
//
// Параметры:
//  ГрафикРаботы	- СправочникСсылка.Календари - график, который необходимо использовать.
//  ДатаОт			- Дата - дата, от которой нужно рассчитать количество дней.
//  МассивДней		- Массив - количество дней (Число), на которые нужно увеличить дату начала.
//  РассчитыватьСледующуюДатуОтПредыдущей	- Булево - нужно ли рассчитывать следующую дату от предыдущей, 
//											           или все даты рассчитываются от переданной даты.
//  ВызыватьИсключение - Булево - если Истина, вызывается исключение в случае незаполненного графика.
//
// Возвращаемое значение:
//  Массив, Неопределено - даты, увеличенные на количество дней, входящих в график ГрафикРаботы.
//	                       Если график ГрафикРаботы не заполнен, и ВызыватьИсключение = Ложь, возвращается Неопределено.
//
Функция ДатыПоГрафику(Знач ГрафикРаботы, Знач ДатаОт, Знач МассивДней, 
	Знач РассчитыватьСледующуюДатуОтПредыдущей = Ложь, ВызыватьИсключение = Истина) Экспорт
	
	СдвигДней = КалендарныеГрафики.ПриращениеДней(МассивДней, РассчитыватьСледующуюДатуОтПредыдущей);
	
	Запрос = Новый Запрос;
	Запрос.УстановитьПараметр("ДатаОт", НачалоДня(ДатаОт));
	Запрос.УстановитьПараметр("ГрафикРаботы", ГрафикРаботы);
	Запрос.УстановитьПараметр("Дни", СдвигДней.ПриращениеДней.ВыгрузитьКолонку("КоличествоДней"));
	Запрос.Текст =
		"ВЫБРАТЬ ПЕРВЫЕ 0
		|	КалендарныеГрафики.ДатаГрафика КАК Дата
		|ИЗ
		|	РегистрСведений.КалендарныеГрафики КАК КалендарныеГрафики
		|ГДЕ
		|	КалендарныеГрафики.ДатаГрафика > &ДатаОт
		|	И КалендарныеГрафики.Календарь = &ГрафикРаботы
		|	И КалендарныеГрафики.ДеньВключенВГрафик
		|
		|УПОРЯДОЧИТЬ ПО
		|	Дата";

	// Ограничим выборку максимальной величиной сдвига для того, чтобы исключить избыточную выборку дней.
	СхемаЗапроса = Новый СхемаЗапроса();
	СхемаЗапроса.УстановитьТекстЗапроса(Запрос.Текст);
	СхемаЗапроса.ПакетЗапросов[0].Операторы[0].КоличествоПолучаемыхЗаписей = СдвигДней.Максимум;
	Запрос.Текст = СхемаЗапроса.ПолучитьТекстЗапроса();
	
	ЗапрошенныеДни = Новый Соответствие();
	Для Каждого СтрокаТаблицы Из СдвигДней.ПриращениеДней Цикл
		ЗапрошенныеДни.Вставить(СтрокаТаблицы.КоличествоДней, Ложь);
	КонецЦикла;
	
	Выборка = Запрос.Выполнить().Выбрать();
	Если Выборка.Количество() < СдвигДней.Максимум Тогда
		Если ВызыватьИсключение Тогда
			ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'График работы ""%1"" не заполнен с даты %2 на указанное количество рабочих дней.'"), 
				ГрафикРаботы, 
				Формат(ДатаОт, "ДЛФ=D"));
		Иначе
			Возврат Неопределено;
		КонецЕсли;
	КонецЕсли;
	
	Дней = 0;
	Пока Выборка.Следующий() Цикл
		Дней = Дней + 1;
		Если ЗапрошенныеДни[Дней] = Ложь Тогда
			ЗапрошенныеДни.Вставить(Дней, Выборка.Дата);
		КонецЕсли;
	КонецЦикла;
	
	МассивДат = Новый Массив;
	Для Каждого СтрокаТаблицы Из СдвигДней.ПриращениеДней Цикл
		Дата = ЗапрошенныеДни[СтрокаТаблицы.КоличествоДней];
		ОбщегоНазначенияКлиентСервер.Проверить(ТипЗнч(Дата) = Тип("Дата") И ЗначениеЗаполнено(Дата));
		МассивДат.Добавить(Дата);
	КонецЦикла;
	
	Возврат МассивДат;
	
КонецФункции

// Возвращает дату, которая отличается указанной даты ДатаОт на количество дней,
// входящих в указанный график ГрафикРаботы.
//
// Параметры:
//  ГрафикРаботы	- СправочникСсылка.Календари - график, который необходимо использовать.
//  ДатаОт			- Дата - дата, от которой нужно рассчитать количество дней.
//  КоличествоДней	- Число - количество дней, на которые нужно увеличить дату начала ДатаОт.
//  ВызыватьИсключение - Булево - если Истина, вызывается исключение в случае незаполненного графика.
//
// Возвращаемое значение:
//  Дата, Неопределено - дата, увеличенная на количество дней, входящих в график ГрафикРаботы.
//	                     Если график ГрафикРаботы не заполнен, и ВызыватьИсключение = Ложь, возвращается Неопределено.
//
Функция ДатаПоГрафику(Знач ГрафикРаботы, Знач ДатаОт, Знач КоличествоДней, ВызыватьИсключение = Истина) Экспорт
	
	ДатаОт = НачалоДня(ДатаОт);
	
	Если КоличествоДней = 0 Тогда
		Возврат ДатаОт;
	КонецЕсли;
	
	МассивДней = Новый Массив;
	МассивДней.Добавить(КоличествоДней);
	
	МассивДат = ДатыПоГрафику(ГрафикРаботы, ДатаОт, МассивДней, , ВызыватьИсключение);
	
	Возврат ?(МассивДат <> Неопределено, МассивДат[0], Неопределено);
	
КонецФункции

// Конструктор параметров получения ближайших к заданным дат, включенных в график.
//  См. БлижайшиеРабочиеДаты.
// 
// Возвращаемое значение:
//  Структура:
//   * ПолучатьПредшествующие - Булево - способ получения ближайшей даты:
//       если Истина - определяются рабочие даты, предшествующие переданным в параметре НачальныеДаты,
//       если Ложь - получаются ближайшие рабочие даты, следующие за начальными датами.
//       Значение по умолчанию - Ложь:
//   * ИгнорироватьНезаполненностьГрафика - Булево - если Истина, то в любом случае будет возвращено соответствие.
//       Начальные даты, для которых не будет значений из-за незаполненности графика, включены не будут.
//       Значение по умолчанию - Ложь:
//   * ВызыватьИсключение - Булево - вызов исключения в случае не заполненного графика
//       если Истина, вызвать исключение, если график не заполнен.
//       если Ложь - даты, по которым не удалось определить ближайшую дату, будут просто пропущены.
//       Значение по умолчанию - Истина.
//
Функция ПараметрыПолученияБлижайшихДатПоГрафику() Экспорт
	Параметры = Новый Структура(
		"ПолучатьПредшествующие,
		|ИгнорироватьНезаполненностьГрафика,
		|ВызыватьИсключение");
	Параметры.ПолучатьПредшествующие = Ложь;
	Параметры.ИгнорироватьНезаполненностьГрафика = Ложь;
	Параметры.ВызыватьИсключение = Истина;
	Возврат Параметры;
КонецФункции

// Определяет для каждой даты дату ближайшего к дня, включенного в график.
//
// Параметры:
//  ГрафикРаботы		 - СправочникСсылка.Календари
//  НачальныеДаты		 - Массив из Дата - даты, относительно которых нужно найти ближайшие.
//  ПараметрыПолучения	 - см. ПараметрыПолученияБлижайшихДатПоГрафику.
// 
// Возвращаемое значение:
//  Соответствие из КлючИЗначение:
//   * Ключ - Дата - начальная дата.
//   * Значение - Дата - ближайшая к ней дата, включенная в график.
//
Функция БлижайшиеДатыВключенныеВГрафик(ГрафикРаботы, НачальныеДаты, ПараметрыПолучения = Неопределено) Экспорт
	
	Если ПараметрыПолучения = Неопределено Тогда
		ПараметрыПолучения = ПараметрыПолученияБлижайшихДатПоГрафику();
	КонецЕсли;
	
	ОбщегоНазначенияКлиентСервер.ПроверитьПараметр(
		"ГрафикиРаботы.БлижайшиеДатыВключенныеВГрафик", 
		"ГрафикРаботы", 
		ГрафикРаботы, 
		Тип("СправочникСсылка.Календари"));

	ОбщегоНазначенияКлиентСервер.Проверить(
		ЗначениеЗаполнено(ГрафикРаботы), 
		НСтр("ru = 'Не указан график работы.'"), 
		"ГрафикиРаботы.БлижайшиеДатыВключенныеВГрафик");
	
	ТекстЗапросаВТ = "";
	ПерваяЧасть = Истина;
	Для Каждого НачальнаяДата Из НачальныеДаты Цикл
		Если Не ЗначениеЗаполнено(НачальнаяДата) Тогда
			Продолжить;
		КонецЕсли;
		Если Не ПерваяЧасть Тогда
			ТекстЗапросаВТ = ТекстЗапросаВТ + "
			|ОБЪЕДИНИТЬ ВСЕ
			|";
		КонецЕсли;
		Если ПерваяЧасть Тогда
			ТекстЗапроса = "
			|ВЫБРАТЬ
			|	ДАТАВРЕМЯ(2000,01,01) КАК Дата 
			|ПОМЕСТИТЬ ВТНачальныеДаты
			|";
		Иначе	
			ТекстЗапроса = "
			|ВЫБРАТЬ
			|	ДАТАВРЕМЯ(2000,01,01)";
		КонецЕсли;
		ТекстЗапросаВТ = ТекстЗапросаВТ + СтрЗаменить(ТекстЗапроса, "2000,01,01", 
			Формат(НачальнаяДата, "ДФ=гггг,ММ,дд"));
		ПерваяЧасть = Ложь;
	КонецЦикла;

	Если ПустаяСтрока(ТекстЗапросаВТ) Тогда
		Возврат Новый Соответствие;
	КонецЕсли;

	Запрос = Новый Запрос(ТекстЗапросаВТ);
	Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	Запрос.Выполнить();
	
	ТекстЗапроса = 
		"ВЫБРАТЬ
		|	НачальныеДаты.Дата,
		|	МИНИМУМ(ДатыКалендаря.ДатаГрафика) КАК БлижайшаяДата
		|ИЗ
		|	ВТНачальныеДаты КАК НачальныеДаты
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КалендарныеГрафики КАК ДатыКалендаря
		|		ПО (ДатыКалендаря.ДатаГрафика >= НачальныеДаты.Дата)
		|			И (ДатыКалендаря.Календарь = &График)
		|			И (ДатыКалендаря.ДеньВключенВГрафик)
		|
		|СГРУППИРОВАТЬ ПО
		|	НачальныеДаты.Дата";
	
	Если ПараметрыПолучения.ПолучатьПредшествующие Тогда
		ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "МИНИМУМ(ДатыКалендаря.ДатаГрафика)", "МАКСИМУМ(ДатыКалендаря.ДатаГрафика)");
		ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "ДатыКалендаря.ДатаГрафика >= НачальныеДаты.Дата", "ДатыКалендаря.ДатаГрафика <= НачальныеДаты.Дата");
	КонецЕсли;
	Запрос.Текст = ТекстЗапроса;
	Запрос.УстановитьПараметр("График", ГрафикРаботы);
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	ДатыРабочихДней = Новый Соответствие;
	Пока Выборка.Следующий() Цикл
		Если ЗначениеЗаполнено(Выборка.БлижайшаяДата) Тогда
			ДатыРабочихДней.Вставить(Выборка.Дата, Выборка.БлижайшаяДата);
		Иначе 
			Если ПараметрыПолучения.ИгнорироватьНезаполненностьГрафика Тогда
				Продолжить;
			КонецЕсли;
			Если ПараметрыПолучения.ВызыватьИсключение Тогда
				ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = 'Невозможно определить ближайшую дату, включенную в график, для даты %1, 
						 |возможно, график работы не заполнен.'"), 
					Формат(Выборка.Дата, "ДЛФ=D"));
			Иначе
				Возврат Неопределено;
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	Возврат ДатыРабочихДней;
	
КонецФункции

// Составляет расписания работы для дат, включенных в указанные графики на указанный период.
// Если расписание на предпраздничный день не задано, то оно определяется так, как если бы этот день был бы рабочим.
//
// Параметры:
//  Графики       - Массив - массив элементов типа СправочникСсылка.Календари, для которых составляются расписания.
//  ДатаНачала    - Дата   - дата начала периода, за который нужно составить расписания.
//  ДатаОкончания - Дата   - дата окончания периода.
//
// Возвращаемое значение:
//   ТаблицаЗначений:
//    * ГрафикРаботы    - СправочникСсылка.Календари - график работы.
//    * ДатаГрафика     - Дата - дата в графике работы ГрафикРаботы.
//    * ВремяНачала     - Дата - время начала работы в день ДатаГрафика.
//    * ВремяОкончания  - Дата - время окончания работы в день ДатаГрафика.
//
Функция РасписанияРаботыНаПериод(Графики, ДатаНачала, ДатаОкончания) Экспорт
	
	МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	
	// Создаем временную таблицу расписаний.
	СоздатьВТРасписанияРаботыНаПериод(МенеджерВременныхТаблиц, Графики, ДатаНачала, ДатаОкончания);
	
	ТекстЗапроса = 
	"ВЫБРАТЬ
	|	РасписанияРаботы.ГрафикРаботы,
	|	РасписанияРаботы.ДатаГрафика,
	|	РасписанияРаботы.ВремяНачала,
	|	РасписанияРаботы.ВремяОкончания
	|ИЗ
	|	ВТРасписанияРаботы КАК РасписанияРаботы";
	
	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблиц;
	
	Возврат Запрос.Выполнить().Выгрузить();
	
КонецФункции

// Создает в менеджере временную таблицу ВТРасписанияРаботы с колонками, соответствующими возвращаемому значению
// функции РасписанияРаботыНаПериод.
//
// Параметры:
//  МенеджерВременныхТаблиц - МенеджерВременныхТаблиц - менеджер, в котором будет создана временная таблица.
//  Графики       - Массив - массив элементов типа СправочникСсылка.Календари, для которых составляются расписания.
//  ДатаНачала    - Дата   - дата начала периода, за который нужно составить расписания.
//  ДатаОкончания - Дата   - дата окончания периода.
//
Процедура СоздатьВТРасписанияРаботыНаПериод(МенеджерВременныхТаблиц, Графики, ДатаНачала, ДатаОкончания) Экспорт
	
	ТекстЗапроса = 
	"ВЫБРАТЬ
	|	ШаблонЗаполнения.Ссылка КАК ГрафикРаботы,
	|	МАКСИМУМ(ШаблонЗаполнения.НомерСтроки) КАК ДлинаЦикла
	|ПОМЕСТИТЬ ВТДлинаЦиклаГрафиков
	|ИЗ
	|	Справочник.Календари.ШаблонЗаполнения КАК ШаблонЗаполнения
	|ГДЕ
	|	ШаблонЗаполнения.Ссылка В(&Календари)
	|
	|СГРУППИРОВАТЬ ПО
	|	ШаблонЗаполнения.Ссылка
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	Календари.Ссылка КАК ГрафикРаботы,
	|	ДанныеПроизводственногоКалендаря.Дата КАК ДатаГрафика,
	|	ВЫБОР
	|		КОГДА ДанныеПроизводственногоКалендаря.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный)
	|			ТОГДА ИСТИНА
	|		ИНАЧЕ ЛОЖЬ
	|	КОНЕЦ КАК ПредпраздничныйДень
	|ПОМЕСТИТЬ ВТПредпраздничныеДни
	|ИЗ
	|	РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Календари КАК Календари
	|		ПО ДанныеПроизводственногоКалендаря.ПроизводственныйКалендарь = Календари.ПроизводственныйКалендарь
	|			И (Календари.Ссылка В (&Календари))
	|			И (ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания)
	|			И (ДанныеПроизводственногоКалендаря.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный))
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	Календари.Ссылка КАК ГрафикРаботы,
	|	ДанныеПроизводственногоКалендаря.Дата КАК ДатаГрафика,
	|	ДанныеПроизводственногоКалендаря.ДатаПереноса
	|ПОМЕСТИТЬ ВТДатыПереноса
	|ИЗ
	|	РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Календари КАК Календари
	|		ПО ДанныеПроизводственногоКалендаря.ПроизводственныйКалендарь = Календари.ПроизводственныйКалендарь
	|			И (Календари.Ссылка В (&Календари))
	|			И (ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания)
	|			И (ДанныеПроизводственногоКалендаря.ДатаПереноса <> ДАТАВРЕМЯ(1, 1, 1))
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	КалендарныеГрафики.Календарь КАК ГрафикРаботы,
	|	КалендарныеГрафики.ДатаГрафика КАК ДатаГрафика,
	|	РАЗНОСТЬДАТ(Календари.ДатаОтсчета, КалендарныеГрафики.ДатаГрафика, ДЕНЬ) + 1 КАК ДнейОтДатыОтсчета,
	|	ПредпраздничныеДни.ПредпраздничныйДень,
	|	ДатыПереноса.ДатаПереноса
	|ПОМЕСТИТЬ ВТДниВключенныеВГрафик
	|ИЗ
	|	РегистрСведений.КалендарныеГрафики КАК КалендарныеГрафики
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Календари КАК Календари
	|		ПО КалендарныеГрафики.Календарь = Календари.Ссылка
	|			И (КалендарныеГрафики.Календарь В (&Календари))
	|			И (КалендарныеГрафики.ДатаГрафика МЕЖДУ &ДатаНачала И &ДатаОкончания)
	|			И (КалендарныеГрафики.ДеньВключенВГрафик)
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТПредпраздничныеДни КАК ПредпраздничныеДни
	|		ПО (ПредпраздничныеДни.ГрафикРаботы = КалендарныеГрафики.Календарь)
	|			И (ПредпраздничныеДни.ДатаГрафика = КалендарныеГрафики.ДатаГрафика)
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТДатыПереноса КАК ДатыПереноса
	|		ПО (ДатыПереноса.ГрафикРаботы = КалендарныеГрафики.Календарь)
	|			И (ДатыПереноса.ДатаГрафика = КалендарныеГрафики.ДатаГрафика)
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ДниВключенныеВГрафик.ГрафикРаботы,
	|	ДниВключенныеВГрафик.ДатаГрафика,
	|	ВЫБОР
	|		КОГДА ДниВключенныеВГрафик.РезультатДеленияПоМодулю = 0
	|			ТОГДА ДниВключенныеВГрафик.ДлинаЦикла
	|		ИНАЧЕ ДниВключенныеВГрафик.РезультатДеленияПоМодулю
	|	КОНЕЦ КАК НомерДня,
	|	ДниВключенныеВГрафик.ПредпраздничныйДень
	|ПОМЕСТИТЬ ВТДатыНомераДней
	|ИЗ
	|	(ВЫБРАТЬ
	|		ДниВключенныеВГрафик.ГрафикРаботы КАК ГрафикРаботы,
	|		ДниВключенныеВГрафик.ДатаГрафика КАК ДатаГрафика,
	|		ДниВключенныеВГрафик.ПредпраздничныйДень КАК ПредпраздничныйДень,
	|		ДниВключенныеВГрафик.ДлинаЦикла КАК ДлинаЦикла,
	|		ДниВключенныеВГрафик.ДнейОтДатыОтсчета - ДниВключенныеВГрафик.ЦелаяЧастьРезультатаДеления * ДниВключенныеВГрафик.ДлинаЦикла КАК РезультатДеленияПоМодулю
	|	ИЗ
	|		(ВЫБРАТЬ
	|			ДниВключенныеВГрафик.ГрафикРаботы КАК ГрафикРаботы,
	|			ДниВключенныеВГрафик.ДатаГрафика КАК ДатаГрафика,
	|			ДниВключенныеВГрафик.ПредпраздничныйДень КАК ПредпраздничныйДень,
	|			ДниВключенныеВГрафик.ДнейОтДатыОтсчета КАК ДнейОтДатыОтсчета,
	|			ДлинаЦиклов.ДлинаЦикла КАК ДлинаЦикла,
	|			(ВЫРАЗИТЬ(ДниВключенныеВГрафик.ДнейОтДатыОтсчета / ДлинаЦиклов.ДлинаЦикла КАК ЧИСЛО(15, 0))) - ВЫБОР
	|				КОГДА (ВЫРАЗИТЬ(ДниВключенныеВГрафик.ДнейОтДатыОтсчета / ДлинаЦиклов.ДлинаЦикла КАК ЧИСЛО(15, 0))) > ДниВключенныеВГрафик.ДнейОтДатыОтсчета / ДлинаЦиклов.ДлинаЦикла
	|					ТОГДА 1
	|				ИНАЧЕ 0
	|			КОНЕЦ КАК ЦелаяЧастьРезультатаДеления
	|		ИЗ
	|			ВТДниВключенныеВГрафик КАК ДниВключенныеВГрафик
	|				ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Календари КАК Календари
	|				ПО ДниВключенныеВГрафик.ГрафикРаботы = Календари.Ссылка
	|					И (Календари.СпособЗаполнения = ЗНАЧЕНИЕ(Перечисление.СпособыЗаполненияГрафикаРаботы.ПоЦикламПроизвольнойДлины))
	|				ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТДлинаЦиклаГрафиков КАК ДлинаЦиклов
	|				ПО ДниВключенныеВГрафик.ГрафикРаботы = ДлинаЦиклов.ГрафикРаботы) КАК ДниВключенныеВГрафик) КАК ДниВключенныеВГрафик
	|
	|ОБЪЕДИНИТЬ ВСЕ
	|
	|ВЫБРАТЬ
	|	ДниВключенныеВГрафик.ГрафикРаботы,
	|	ДниВключенныеВГрафик.ДатаГрафика,
	|	ВЫБОР
	|		КОГДА ДниВключенныеВГрафик.ДатаПереноса ЕСТЬ NULL 
	|			ТОГДА ДЕНЬНЕДЕЛИ(ДниВключенныеВГрафик.ДатаГрафика)
	|		ИНАЧЕ ДЕНЬНЕДЕЛИ(ДниВключенныеВГрафик.ДатаПереноса)
	|	КОНЕЦ,
	|	ДниВключенныеВГрафик.ПредпраздничныйДень
	|ИЗ
	|	ВТДниВключенныеВГрафик КАК ДниВключенныеВГрафик
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Календари КАК Календари
	|		ПО ДниВключенныеВГрафик.ГрафикРаботы = Календари.Ссылка
	|ГДЕ
	|	Календари.СпособЗаполнения = ЗНАЧЕНИЕ(Перечисление.СпособыЗаполненияГрафикаРаботы.ПоНеделям)
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	ДниВключенныеВГрафик.ГрафикРаботы,
	|	ДниВключенныеВГрафик.ДатаГрафика,
	|	ДниВключенныеВГрафик.НомерДня,
	|	ЕСТЬNULL(РасписанияРаботыПредпраздничногоДня.ВремяНачала, РасписанияРаботы.ВремяНачала) КАК ВремяНачала,
	|	ЕСТЬNULL(РасписанияРаботыПредпраздничногоДня.ВремяОкончания, РасписанияРаботы.ВремяОкончания) КАК ВремяОкончания
	|ПОМЕСТИТЬ ВТРасписанияРаботы
	|ИЗ
	|	ВТДатыНомераДней КАК ДниВключенныеВГрафик
	|		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Календари.РасписаниеРаботы КАК РасписанияРаботы
	|		ПО (РасписанияРаботы.Ссылка = ДниВключенныеВГрафик.ГрафикРаботы)
	|			И (РасписанияРаботы.НомерДня = ДниВключенныеВГрафик.НомерДня)
	|		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Календари.РасписаниеРаботы КАК РасписанияРаботыПредпраздничногоДня
	|		ПО (РасписанияРаботыПредпраздничногоДня.Ссылка = ДниВключенныеВГрафик.ГрафикРаботы)
	|			И (РасписанияРаботыПредпраздничногоДня.НомерДня = 0)
	|			И (ДниВключенныеВГрафик.ПредпраздничныйДень)
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	ДниВключенныеВГрафик.ГрафикРаботы,
	|	ДниВключенныеВГрафик.ДатаГрафика";
	
	// Для вычисления номера в цикле произвольной длины для дня, включенного в график, используется следующая формула:
	// Номер дня = Дней от даты отсчета % Длина цикла, где % - операция деления по модулю.
	
	// Операция деления по модулю в свою очередь производится по формуле:
	// Делимое - Цел(Делимое / Делитель) * Делитель, где Цел() - функция выделения целой части.
	
	// Для выделения целой части используется конструкция:
	// если результат округления числа по правилам "1.5 как 2" больше исходного значения, уменьшаем результат на 1.
	
	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблиц;
	Запрос.УстановитьПараметр("Календари", Графики);
	Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
	Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
	Запрос.Выполнить();
	
КонецПроцедуры

#КонецОбласти

#Область СлужебныйПрограммныйИнтерфейс

// Выполняет обновление графиков работы по данным производственных календарей, 
// на основании которых они заполняются.
//
// Параметры:
//  - УсловияОбновления - ТаблицаЗначений:
//    - КодПроизводственногоКалендаря - код производственного календаря, данные которого изменились,
//    - Год - год, за который нужно обновить данные.
//
Процедура ОбновитьГрафикиРаботыПоДаннымПроизводственныхКалендарей(УсловияОбновления) Экспорт
	
	РегистрыСведений.КалендарныеГрафики.ОбновитьГрафикиРаботыПоДаннымПроизводственныхКалендарей(УсловияОбновления);
	
КонецПроцедуры

// Добавляет в список блокируемых объектов справочник графиков работы, 
// чтобы на время обновления производственных календарей, графики были недоступны для изменения пользователем.
//
// Параметры:
//  БлокируемыеОбъекты - Массив из Строка - имена метаданных блокируемых объектов.
//
Процедура ЗаполнитьБлокируемыеОбъектыЗависимыеОтПроизводственныхКалендарей(БлокируемыеОбъекты) Экспорт
	
	БлокируемыеОбъекты.Добавить("Справочник.Календари");
	
КонецПроцедуры

// Добавляет в список изменяемых объектов регистр календарных графиков.
//
// Параметры:
//  ИзменяемыеОбъекты - Массив из Строка - имена метаданных изменяемых объектов.
//
Процедура ЗаполнитьИзменяемыеОбъектыЗависимыеОтПроизводственныхКалендарей(ИзменяемыеОбъекты) Экспорт
	
	ИзменяемыеОбъекты.Добавить("РегистрСведений.КалендарныеГрафики");
	
КонецПроцедуры

// Определяет количество дней, входящих в график, для указанного периода.
//
// Параметры:
//   ГрафикРаботы	- СправочникСсылка.Календари - график, который необходимо использовать для расчета дней.
//   ДатаНачала		- Дата - дата начала периода.
//   ДатаОкончания	- Дата - дата окончания периода.
//   ВызыватьИсключение - Булево - если Истина, вызвать исключение в случае незаполненного графика.
//
// Возвращаемое значение:
//   Число		- количество дней между датами начала и окончания.
//	              Если график ГрафикРаботы не заполнен, и ВызыватьИсключение = Ложь, возвращается Неопределено.
//
Функция РазностьДатПоКалендарю(Знач ГрафикРаботы, Знач ДатаНачала, Знач ДатаОкончания, ВызыватьИсключение = Истина) Экспорт
	
	Если Не ЗначениеЗаполнено(ГрафикРаботы) Тогда
		Если ВызыватьИсключение Тогда
			ВызватьИсключение НСтр("ru = 'Не указан график работы.'");
		КонецЕсли;
		Возврат Неопределено;
	КонецЕсли;
	
	ДатаНачала = НачалоДня(ДатаНачала);
	ДатаОкончания = НачалоДня(ДатаОкончания);
	
	ДатыГрафика = Новый Массив;
	ДатыГрафика.Добавить(ДатаНачала);
	Если Год(ДатаНачала) <> Год(ДатаОкончания) И КонецДня(ДатаНачала) <> КонецГода(ДатаНачала) Тогда
		// Если даты разных годов, то добавляем "границы" годов.
		Для НомерГода = Год(ДатаНачала) По Год(ДатаОкончания) - 1 Цикл
			ДатыГрафика.Добавить(Дата(НомерГода, 12, 31));
		КонецЦикла;
	КонецЕсли;
	ДатыГрафика.Добавить(ДатаОкончания);
	
	// Формируем текст запроса временной таблицы, содержащей указанные даты.
	ТекстЗапроса = "";
	Для Каждого ДатаГрафика Из ДатыГрафика Цикл
		Если ПустаяСтрока(ТекстЗапроса) Тогда
			ШаблонОбъединения = 
			"ВЫБРАТЬ
			|	ДАТАВРЕМЯ(2020,01,01) КАК ДатаГрафика
			|ПОМЕСТИТЬ ВТДатыГрафика
			|";
		Иначе
			ШаблонОбъединения = 
			"ОБЪЕДИНИТЬ ВСЕ
			|
			|ВЫБРАТЬ
			|	ДАТАВРЕМЯ(2020,01,01)";
		КонецЕсли;
		ТекстЗапроса = ТекстЗапроса + СтрЗаменить(
			ШаблонОбъединения, "2020,01,01", Формат(ДатаГрафика, "ДФ='гггг,ММ,д'")); // АПК:1367 Форматирование даты в технических целях, не отображается пользователю
	КонецЦикла;
	
	МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	
	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблиц;
	Запрос.Выполнить();
	
	// Готовим временные таблицы с исходными данными.
	Запрос.УстановитьПараметр("ГрафикРаботы", ГрафикРаботы);
	Запрос.Текст =
	"ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	ДатыГрафика.ДатаГрафика КАК ДатаГрафика
	|ПОМЕСТИТЬ ВТРазличныеДатыГрафика
	|ИЗ
	|	ВТДатыГрафика КАК ДатыГрафика
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	ГОД(ДатыГрафика.ДатаГрафика) КАК Год
	|ПОМЕСТИТЬ ВТРазличныеГодыГрафика
	|ИЗ
	|	ВТДатыГрафика КАК ДатыГрафика
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	КалендарныеГрафики.Год КАК Год,
	|	КалендарныеГрафики.ДатаГрафика КАК ДатаГрафика,
	|	КалендарныеГрафики.ДеньВключенВГрафик КАК ДеньВключенВГрафик
	|ПОМЕСТИТЬ ВТКалендарныеГрафики
	|ИЗ
	|	РегистрСведений.КалендарныеГрафики КАК КалендарныеГрафики
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТРазличныеГодыГрафика КАК ГодыГрафика
	|		ПО (ГодыГрафика.Год = КалендарныеГрафики.Год)
	|ГДЕ
	|	КалендарныеГрафики.Календарь = &ГрафикРаботы
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ДатыГрафика.ДатаГрафика КАК ДатаГрафика,
	|	КОЛИЧЕСТВО(ДниВключенныеВГрафик.ДатаГрафика) КАК КоличествоДнейВГрафикеСНачалаГода
	|ПОМЕСТИТЬ ВТКоличествоДнейВключенныхВГрафик
	|ИЗ
	|	ВТРазличныеДатыГрафика КАК ДатыГрафика
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТКалендарныеГрафики КАК ДниВключенныеВГрафик
	|		ПО (ДниВключенныеВГрафик.Год = ГОД(ДатыГрафика.ДатаГрафика))
	|			И (ДниВключенныеВГрафик.ДатаГрафика <= ДатыГрафика.ДатаГрафика)
	|			И (ДниВключенныеВГрафик.ДеньВключенВГрафик)
	|
	|СГРУППИРОВАТЬ ПО
	|	ДатыГрафика.ДатаГрафика
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ДатыГрафика.ДатаГрафика КАК ДатаГрафика,
	|	ЕСТЬNULL(ДанныеГрафика.ДеньВключенВГрафик, ЛОЖЬ) КАК ДеньВключенВГрафик,
	|	ДниВключенныеВГрафик.КоличествоДнейВГрафикеСНачалаГода КАК КоличествоДнейВГрафикеСНачалаГода
	|ИЗ
	|	ВТДатыГрафика КАК ДатыГрафика
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТКалендарныеГрафики КАК ДанныеГрафика
	|		ПО (ДанныеГрафика.Год = ГОД(ДатыГрафика.ДатаГрафика))
	|			И (ДанныеГрафика.ДатаГрафика = ДатыГрафика.ДатаГрафика)
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТКоличествоДнейВключенныхВГрафик КАК ДниВключенныеВГрафик
	|		ПО (ДниВключенныеВГрафик.ДатаГрафика = ДатыГрафика.ДатаГрафика)
	|
	|УПОРЯДОЧИТЬ ПО
	|	ДатыГрафика.ДатаГрафика";
	
	Результат = Запрос.Выполнить();
	
	Если Результат.Пустой() Тогда
		Если ВызыватьИсключение Тогда
			СообщениеОбОшибке = НСтр("ru = 'График работы ""%1"" не заполнен на период %2.'");
			ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(СообщениеОбОшибке, ГрафикРаботы, ПредставлениеПериода(ДатаНачала, КонецДня(ДатаОкончания)));
		Иначе
			Возврат Неопределено;
		КонецЕсли;
	КонецЕсли;
	
	Выборка = Результат.Выбрать();
	
	// Получаем выборку, в которой для каждой исходной даты определено количество дней, 
	// включенных в график с начала года.
	// Из значения, заданного на первую дату выборки вычитаем все последующие, 
	// получая таким образом количество дней, включенных в график за весь период со знаком минус.
	// Если первый день выборки является рабочим, а последующий - выходным, 
	// то количество дней включенных на обе эти даты будет одинаковым, 
	// в этом случае для корректировки добавляем к итоговому значению 1 день.
	
	КоличествоДнейВГрафике = Неопределено;
	ДобавлятьПервыйДень = Ложь;
	
	Пока Выборка.Следующий() Цикл
		Если КоличествоДнейВГрафике = Неопределено Тогда
			КоличествоДнейВГрафике = Выборка.КоличествоДнейВГрафикеСНачалаГода;
			ДобавлятьПервыйДень = Выборка.ДеньВключенВГрафик;
		Иначе
			КоличествоДнейВГрафике = КоличествоДнейВГрафике - Выборка.КоличествоДнейВГрафикеСНачалаГода;
		КонецЕсли;
	КонецЦикла;
	
	Возврат - КоличествоДнейВГрафике + ?(ДобавлятьПервыйДень, 1, 0);
	
КонецФункции

Функция ИмяПроцедурыОбновленияГрафиковРаботы() Экспорт
	
	Возврат "РегистрыСведений.КалендарныеГрафики.ОбработатьДанныеДляПереходаНаНовуюВерсию";
	
КонецФункции

Функция ИмяПроцедурыУстановкиПризнакаУчитыватьНерабочиеДни() Экспорт
	
	Возврат "Справочники.Календари.ОбработатьДанныеДляПереходаНаНовуюВерсию";
	
КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Обработчики событий подсистем конфигурации.

// См. ОбновлениеИнформационнойБазыБСП.ПриДобавленииОбработчиковОбновления.
Процедура ПриДобавленииОбработчиковОбновления(Обработчики) Экспорт
	
	Если Метаданные.Обработки.Найти("ЗаполнениеГрафиковРаботы") <> Неопределено Тогда
		МодульЗаполнениеГрафиковРаботы = ОбщегоНазначения.ОбщийМодуль("Обработки.ЗаполнениеГрафиковРаботы");
		МодульЗаполнениеГрафиковРаботы.ПриДобавленииОбработчиковОбновления(Обработчики);
	КонецЕсли;
	
	Обработчик = Обработчики.Добавить();
	Обработчик.Версия = "3.1.2.78";
	Обработчик.Процедура = "РегистрыСведений.КалендарныеГрафики.ОбработатьДанныеДляПереходаНаНовуюВерсию";
	Обработчик.ПроцедураЗаполненияДанныхОбновления = "РегистрыСведений.КалендарныеГрафики.ЗарегистрироватьДанныеКОбработкеДляПереходаНаНовуюВерсию";
	Обработчик.РежимВыполнения = "Отложенно";
	Обработчик.ЗапускатьИВПодчиненномУзлеРИБСФильтрами = Истина;
	Обработчик.ЧитаемыеОбъекты = "РегистрСведений.КалендарныеГрафики, РегистрСведений.РучныеИзмененияГрафиковРаботы, РегистрСведений.ДанныеПроизводственногоКалендаря";
	Обработчик.ИзменяемыеОбъекты = "РегистрСведений.КалендарныеГрафики";
	Обработчик.ПроцедураПроверки = "ОбновлениеИнформационнойБазы.ДанныеОбновленыНаНовуюВерсиюПрограммы";
	Обработчик.Идентификатор = Новый УникальныйИдентификатор("39e6fbf8-c02d-4459-bdbd-58adf6c6127c");
	Обработчик.Комментарий = НСтр("ru = 'Исправление включения в график рабочих и предпраздничных дней, выпадающих на субботу или воскресенье.'");
	Обработчик.БлокируемыеОбъекты = "Справочник.Календари";
	Обработчик.ПриоритетыВыполнения = ОбновлениеИнформационнойБазы.ПриоритетыВыполненияОбработчика();
	
	Если КалендарныеГрафики.ЕстьИзменяемыеОбъектыЗависимыеОтПроизводственныхКалендарей() Тогда
		Приоритет = Обработчик.ПриоритетыВыполнения.Добавить();
		Приоритет.Процедура = "КалендарныеГрафики.ОбновитьДанныеЗависимыеОтПроизводственныхКалендарей";
		Приоритет.Порядок = "После";
	КонецЕсли;
	
	Обработчик = Обработчики.Добавить();
	Обработчик.Версия = "3.1.3.94";
	Обработчик.Процедура = "Справочники.Календари.ОбработатьДанныеДляПереходаНаНовуюВерсию";
	Обработчик.ПроцедураЗаполненияДанныхОбновления = "Справочники.Календари.ЗарегистрироватьДанныеКОбработкеДляПереходаНаНовуюВерсию";
	Обработчик.РежимВыполнения = "Отложенно";
	Обработчик.ЗапускатьИВПодчиненномУзлеРИБСФильтрами = Истина;
	Обработчик.ЧитаемыеОбъекты = "Справочник.Календари";
	Обработчик.ИзменяемыеОбъекты = "Справочник.Календари";
	Обработчик.ПроцедураПроверки = "ОбновлениеИнформационнойБазы.ДанныеОбновленыНаНовуюВерсиюПрограммы";
	Обработчик.Идентификатор = Новый УникальныйИдентификатор("fe8c3f3a-8973-4538-a993-ba74fa9162d8");
	Обработчик.Комментарий = НСтр("ru = 'Установка нового признака ""Учитывать нерабочие периоды"" в положение по умолчанию.'");
	Обработчик.БлокируемыеОбъекты = "Справочник.Календари";
	Обработчик.ПриоритетыВыполнения = ОбновлениеИнформационнойБазы.ПриоритетыВыполненияОбработчика();
	
	Если КалендарныеГрафики.ЕстьИзменяемыеОбъектыЗависимыеОтПроизводственныхКалендарей() Тогда
		Приоритет = Обработчик.ПриоритетыВыполнения.Добавить();
		Приоритет.Процедура = "КалендарныеГрафики.ОбновитьДанныеЗависимыеОтПроизводственныхКалендарей";
		Приоритет.Порядок = "До";
	КонецЕсли;
	
КонецПроцедуры

// См. ПользователиПереопределяемый.ПриОпределенииНазначенияРолей
Процедура ПриОпределенииНазначенияРолей(НазначениеРолей) Экспорт
	
	// СовместноДляПользователейИВнешнихПользователей.
	НазначениеРолей.СовместноДляПользователейИВнешнихПользователей.Добавить(
		Метаданные.Роли.ЧтениеГрафиковРаботы.Имя);
	
КонецПроцедуры

// См. ПоискИУдалениеДублей.ТипыИсключаемыеИзВозможныхДублей
Процедура ПриДобавленииТиповИсключаемыхИзВозможныхДублей(ИсключаемыеТипы) Экспорт

	ОбщегоНазначенияКлиентСервер.ДополнитьМассив(
		ИсключаемыеТипы, Метаданные.ОпределяемыеТипы.ВладелецГрафикаРаботы.Тип.Типы()); 

КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

Функция ДвенадцатиЧасовойФорматВремени() Экспорт
	
	ПредставлениеВремени = Формат(ТекущаяДатаСеанса(), "ДЛФ=T");
	Если ВРег(Прав(ПредставлениеВремени, 1)) = "M" Тогда
		Возврат Истина;
	КонецЕсли;
	
	Возврат Ложь;
	
КонецФункции

#КонецОбласти
